iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Modern Web

Vue 演進驗證 × AI Coding 時代的工程實踐系列 第 17

Day 17:建立 VDOM Stress Test Scenario

  • 分享至 

  • xImage
  •  
大量 UI Rendering 時,Vue Runtime 的成本在哪裡?新版 Runtime 是否真的改善?

商品列表、Dashboard、CRM、管理後台,都可能同時渲染數百甚至數千個 Card、Row 或 Cell,難免 一次就要處理大量 UI。

實務上最常見 UI 從 100 個增加到 5,000 個時網頁載入速度變慢,成本究竟增加在哪裡?

因此 Day 17 建立第三個 Validation Scenario:VDOM Stress Test

只留下大量 UI Rendering 把問題縮小


如果直接拿真實專案測試,通常會同時包含:

  • API Request
  • Pinia / Store
  • Computed
  • Watch
  • Business Logic
  • Component Logic
  • Vue Rendering
  • Browser Rendering

最後即使得到一個 120ms,也很難判斷這個數字代表什麼,所以這次反過來處理,刻意建立一個足夠簡單可以控制變因的壓力測試來取代一個完整的真實專案。

                VDOM Stress Test
                       │
              ┌────────┴────────┐
              │                 │
        Generate Data       Render UI
              │                 │
              └────────┬────────┘
                       ▼
              100 / 500 / 1,000 / 5,000

Scenario 只保留 資料產生 → Vue Render,刻意不加入 API、Pinia、Router、Watch、Computed Chain 或額外最佳化,這樣當 Render Count 改變時,主要改變的是 一次需要處理多少 UI。

Scenario 的操作也保持單純


畫面只需要改變 Render Count,再觸發一次 Render。

VDOM Stress Test

核心程式維持最小化:

const cards = ref<Card[]>([])
const renderDuration = ref(0)

async function triggerRender(count: number) {
  const start = performance.now()

  cards.value = generateCards(count)

  await nextTick()

  const end = performance.now()

  renderDuration.value = end - start
}

這裡的 performance.now() 是第一層量測,主要觀察 這次操作從開始,到 Vue 完成這次更新 flush,花了多久

例如:

Render Count = 1,000

renderDuration
      │
      ▼
    120 ms

這個數字很有用,但還不夠。

120ms 是 Vue Runtime 花的嗎?


renderDuration = 120ms,只能表示這次操作觀察到約 120ms 的時間,這段時間裡可能包含不同工作:

120 ms
│
├── Application JavaScript
│
├── Vue Update / Patch
│
└── 其他同步工作

而且:

await nextTick()

代表 Vue 的更新 flush 已經完成,不代表瀏覽器已經完成 Layout、Paint,也不代表這個畫面已經完成最後的視覺呈現。

所以 renderDuration 能回答的是 「花多久」,但是還不能回答 「花在哪裡?」,這個差異會直接影響後面的 Vue 3.5 / 3.6 比較。

Mount 和 Update 必須分開


大量 UI Rendering 還有另一個容易被忽略的問題:第一次建立 5,000 個 UI,和已經存在 5,000 個 UI 後再次更新,不是同一件事。

第一次需要建立大量 UI:

Mount

100
 ↓
500
 ↓
1,000
 ↓
5,000

而 Update 則是:

Update

5,000 個 UI 已經存在
        │
        ▼
再次觸發 Render

因此這個 Scenario 不把兩種成本混成一個數字,而是分開觀察:

測試 主要問題
Mount 大量 UI 第一次建立時,需要多少成本?
Update 大量 UI 已存在,再次更新時需要多少成本?

否則最後得到 5,000 UI = 300ms ,我們還是不知道這 300ms 到底主要發生在第一次建立,還是後續更新。

真正要追的是「成本分布」


假設測到:

N = 1,000

Render Duration
└── 120 ms

這個結果只能說 這次操作很花時間,但這次實驗真正想回答的是:

120 ms
│
├── JavaScript?
│
├── Vue Update / Patch?
│
├── Recalculate Style?
│
├── Layout?
│
└── Paint?

這也是 VDOM Stress Test 和前面 Scenario 的重要差異,前面的實驗比較偏向 某種工作是不是變多、變慢?

這一次則進一步追問:

大量 UI 產生的成本,究竟落在哪一層?

Vue Runtime 和 Browser Rendering 要分開


一次 UI Rendering,可以先用下面這個方式理解:

                  Render
                    │
          ┌─────────┴─────────┐
          ▼                   ▼
   Vue / JavaScript      Browser Rendering
          │                   │
          │             ┌─────┼─────┐
          │             ▼     ▼     ▼
          │           Style Layout Paint
          │
          └── Update / Patch

這裡有一個很重要的觀念:

Chrome Performance 裡的 Rendering,不是 Vue Runtime。

Rendering 主要描述瀏覽器自己的 Rendering 工作。

同樣地:

renderDuration 也不是純粹的 Vue Runtime 時間。

因此這次實驗會使用不同層級的資料回答不同問題:

量測 回答的問題
renderDuration 這次操作總共花多久?
JavaScript / Scripting JavaScript 執行花多少?
Vue Runtime Vue 本身的更新工作佔多少?
Rendering Browser Rendering 花多少?
Layout / Paint 瀏覽器後續工作花多少?

這樣後面比較 Vue 3.5 與 Vue 3.6 時,才不會看到一個數字下降,就直接下結論 Vue 3.6 變快了,我們真正要確認的是 下降的是 Vue Runtime Cost,還是 Browser Rendering Cost?

所以,Chrome Performance Trace 是必要的


到這裡,performance.now() 的角色就很清楚了。

performance.now()
        │
        ▼
知道「花多久」
        │
        │
        ▼
Chrome Performance Trace
        │
        ▼
進一步追「花在哪裡」

因此除了 renderDuration,Scenario 也會收集 Chrome Performance Trace。

Chrome Performance Trace

例如先從 Main Thread 觀察:

Main Thread
────────────────────────────

Scripting
████████████
Rendering
██████████████████
Painting
████

再進一步拆解 Browser Rendering:

Rendering
   │
   ├── Recalculate Style
   ├── Layout
   └── Paint

這些資訊才能讓我們開始區分 大量 UI 的成本,主要落在 JavaScript / Vue,還是瀏覽器 Rendering?

為什麼這裡開始需要 CDP?


如果只測一次,手動開 Chrome DevTools 錄製 Performance Trace 當然可以,但接下來的 Validation 不是只測一次,而是:

Vue 3.5
×
100 / 500 / 1,000 / 5,000
×
Mount / Update
×
多次測量

接著還要用相同流程測不同版本 Vue 3.6,如果全部依靠人工:

操作 UI
  ↓
開始錄 Trace
  ↓
觸發 Render
  ↓
停止 Trace
  ↓
儲存結果
  ↓
下一次

測試流程本身就可能開始引入變異,因此後續需要把這個流程自動化,這也是 CDP 出現的原因。

CDP(Chrome DevTools Protocol)可以讓測試程式控制 Chrome,執行固定操作並收集 Performance Trace。

今天沒有進入 CDP 實作,這裡只需要建立一個量測上的前提:

當 Validation 需要大量、重複、可控的 Trace 時,量測流程本身也必須被控制。

後面的 Validation 才會真正用到這套機制。

先把大量 UI 很慢變成可驗證的問題


因此,今天建立的 VDOM Stress Test 最終固定成:

                VDOM Stress Test
                       │
          ┌────────────┴────────────┐
          ▼                         ▼
       Mount                     Update
          │                         │
          └────────────┬────────────┘
                       ▼
              100 / 500 / 1,000 / 5,000
                       │
          ┌────────────┴────────────┐
          ▼                         ▼
   Render Duration            Chrome Trace
          │                         │
     「花多久」                 「花在哪」
                                    │
                         ┌──────────┴──────────┐
                         ▼                     ▼
                  Vue / JavaScript      Browser Rendering

這個 Scenario 的目的是把原本很模糊的問題 「大量 UI 為什麼會卡?」,轉換成可以重複測量的問題:

當 Render Count 從 100、500、1,000 增加到 5,000 時,Mount 與 Update 的成本如何變化?

再進一步確認:

這些增加的成本,主要發生在 Vue Runtime,還是 Browser Rendering?

最後才有辦法知道 如果換成 Vue 3.6,改善的究竟是哪一層?

GitHub Repo.



上一篇
Day 16:大量列表為什麼卡?
下一篇
Day 18:大量 UI Rendering 的成本在哪裡?
系列文
Vue 演進驗證 × AI Coding 時代的工程實踐23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言